// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE

//sqlquery模式定义
const sqlqueryOpts = {
    tables: null,
    allTables: null,
    sqlTables: null,
    tempSqlTables: null,
    schemaTypes: null,
    showPath: true,
    showType: true,
    titleMaxLength: 20,
    likeMatch: true,
    schemaOpen: false,
    schemaQuery: 3,
    limitTableNum: 50,
    limitNum: 2000,
    defaultTable: null,
    keywords: null,
    identifierQuote: null,
    CONS : {
      QUERY_DIV: ";",
      ALIAS_KEYWORD: "AS"
    },
    customCamelCases: false,
    autoCamelCases: false,
    defaultMain: "defaultMain",
    defaultMainTitle: "默认表别名",
    alisaRuleStr: "[a-z]+[0-9]*",
    codeRuleStr: "((\"[\u4E00-\u9FA50-9a-z _]+\")|(\\[[\u4E00-\u9FA50-9a-z _]+\\])|(`[\u4E00-\u9FA50-9a-z _]+`)|([a-z]+[0-9]*(_[a-z]+[0-9]*)*))",
    codeAlisaRuleStr: "((\"[\u4E00-\u9FA50-9a-z _]+\")|(\\[[\u4E00-\u9FA50-9a-z _]+\\])|(`[\u4E00-\u9FA50-9a-z _]+`)|([a-z]+[0-9]*(_[a-z]+[0-9]*)*))",
    tableRuleStr: "\"%s\"|\\[%s\\]|`%s`|\\b%s\\b",
    schemaTableRuleStr: "\"\\w+\".\"%s\"|\\[\\w+\\].\\[%s\\]|`\\w+`.`%s`|\\b\\w+.%s\\b",
    bracketRuleStr: "^(\"|`|\\[)",
    bracketRuleClear: new RegExp("[\"`\\[\\]]+","g"),
    alisaTempKey: "ALISAALISAALISA",
    regxSemicolon : new RegExp("(^;)|(;$)","g"),
    regxComment : new RegExp("^ *(--|//|#).*","g"),
    regxComment2 : new RegExp("(--|//|#).*$","g"),
    
    schemaType: null,
    tmpSearch: null,
    currSelectQueryStr: null,
    currInputAlias: null,
  
}

var CodeMirror;

function sqlqueryHintHandler(CodeMirrorx) {
    CodeMirror=CodeMirrorx;
    
    function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
    
    function getKeywords(editor) {
        var mode = editor.options.sqlMode || editor.options.mode || editor.doc.modeOption;
        if (mode === "sqlquery") mode = "text/x-sql";
        return CodeMirror.resolveMode(mode).keywords;
    }
    
    function getIdentifierQuote(editor) {
      var mode = editor.options.sqlMode || editor.options.mode || editor.doc.modeOption;
      if (mode === "sqlquery") mode = "text/x-sql";
      return CodeMirror.resolveMode(mode).identifierQuote || "`";
    }
  
    function getText(item) {
      return typeof item == "string" ? item : item.text;
    }
  
    function wrapTable(name, value) {
      if (isArray(value)) value = {columns: value}
      if (!value.text) value.text = name
      return value
    }
  
    function parseTables(input) {
      var result = {}
      if (isArray(input)) {
        for (var i = input.length - 1; i >= 0; i--) {
          var item = input[i]
          result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
        }
      } else if (input) {
        for (var name in input)
          result[name.toUpperCase()] = wrapTable(name, input[name])
      }
      return result
    }
  
    function getTable(name) {
      return sqlqueryOpts.tables[name.toUpperCase()]
    }
  
    function shallowClone(object) {
      var result = {};
      for (var key in object) if (object.hasOwnProperty(key))
        result[key] = object[key];
      return result;
    }
  
    function match(string, word) {
      var gval=getText(word);
      if(!gval){
        return false;
      }
      var upperMatch,word2=gval.toUpperCase();
      if(!sqlqueryOpts.likeMatch && !string.startsWith(".") && sqlqueryOpts.schemaType && word2.startsWith(sqlqueryOpts.schemaType.toUpperCase()+".")
        && word2.indexOf(string.toUpperCase())>-1){
        return true;
      }
      else if(string.startsWith(".") && sqlqueryOpts.tmpSearch.length>0){
          string=sqlqueryOpts.tmpSearch;
      }else{
          upperMatch=string.length>1 && string.indexOf(".")>-1 && !string.startsWith(".");
      }
      if(!sqlqueryOpts.likeMatch || upperMatch){
        var sub = gval.substr(0, string.length);
        return string.toUpperCase() === sub.toUpperCase();
      }else{
        return sqlqueryUtils.checkLikeOrder(string,gval);
      }
    }
    
    function addMatches(result, search, wordlist, formatter) {
      if (isArray(wordlist)) {
        for (var i = 0; i < wordlist.length; i++)
          if (match(search, wordlist[i]) && result.length<(sqlqueryOpts.limitNum+15)) result.push(formatter(wordlist[i]))
      } else {
        for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
          var val = wordlist[word]
          if (!val || val === true)
            val = word
          else
            val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
          if (match(search, val) && result.length<(sqlqueryOpts.limitNum+15)) result.push(formatter(val))
        }
      }
    }
  

  
    function insertIdentifierQuotes(name) {
      var nameParts = getText(name).split(".");
      for (var i = 0; i < nameParts.length; i++)
        nameParts[i] = sqlqueryOpts.identifierQuote +
          // duplicate identifierQuotes
          nameParts[i].replace(new RegExp(sqlqueryOpts.identifierQuote,"g"), sqlqueryOpts.identifierQuote+sqlqueryOpts.identifierQuote) +
          sqlqueryOpts.identifierQuote;
      var escaped = nameParts.join(".");
      if (typeof name == "string") return escaped;
      name = shallowClone(name);
      name.text = escaped;
      return name;
    }
  
    function nameCompletion(cur, token, result, editor) {
      // Try to complete table, column names and return start position of completion
      var useIdentifierQuotes = false;
      var nameParts = [];
      var start = token.start;
      var cont = true;
      while (cont) {
        cont = (token.string.charAt(0) == ".");
        useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == sqlqueryOpts.identifierQuote);
  
        start = token.start;
        nameParts.unshift(sqlqueryUtils.cleanName(token.string));
  
        token = editor.getTokenAt(CodeMirror.Pos(cur.line, token.start));
        if (token.string == ".") {
          cont = true;
          token = editor.getTokenAt(CodeMirror.Pos(cur.line, token.start));
        }
      }
  
      // Try to complete table names
      var string = nameParts.join(".");
      addMatches(result, string, sqlqueryOpts.tables, function(w) {
        return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
      });
  
      // Try to complete columns from defaultTable
      addMatches(result, string, sqlqueryOpts.defaultTable, function(w) {
        return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
      });
  
      // Try to complete columns
      string = nameParts.pop();
      var table = nameParts.join(".");
  
      var alias = false;
      var aliasTable = table;
      // Check if table is available. If not, find table by Alias
      if (!getTable(table)) {
        var oldTable = table;
        table = findTableByAlias(table, editor);
        if (table !== oldTable) alias = true;
      }
  
      var columns = getTable(table);
      if (columns && columns.columns)
        columns = columns.columns;
  
      if (columns) {
        addMatches(result, string, columns, function(w) {
          var tableInsert = table;
          if (alias == true) tableInsert = aliasTable;
          if (typeof w == "string") {
            w = tableInsert + "." + w;
          } else {
            w = shallowClone(w);
            w.text = tableInsert + "." + w.text;
          }
          return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
        });
      }
  
      return start;
    }
  
    function eachWord(lineText, f) {
      var words = lineText.split(/\s+/)
      for (var i = 0; i < words.length; i++)
        if (words[i]) f(words[i].replace(/[`,;]/g, ''))
    }
  
    function findTableByAlias(alias, editor) {
      var doc = editor.doc;
      var fullQuery = doc.getValue();
      var aliasUpperCase = alias.toUpperCase();
      var previousWord = "";
      var table = "";
      var separator = [];
      var validRange = {
        start: CodeMirror.Pos(0, 0),
        end: CodeMirror.Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
      };
  
      //add separator
      var indexOfSeparator = fullQuery.indexOf(sqlqueryOpts.CONS.QUERY_DIV);
      while(indexOfSeparator != -1) {
        separator.push(doc.posFromIndex(indexOfSeparator));
        indexOfSeparator = fullQuery.indexOf(sqlqueryOpts.CONS.QUERY_DIV, indexOfSeparator+1);
      }
      separator.unshift(CodeMirror.Pos(0, 0));
      separator.push(CodeMirror.Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
  
      //find valid range
      var prevItem = null;
      var current = editor.getCursor()
      for (var i = 0; i < separator.length; i++) {
        if ((prevItem == null || CodeMirror.cmpPos(current, prevItem) > 0) && CodeMirror.cmpPos(current, separator[i]) <= 0) {
          validRange = {start: prevItem, end: separator[i]};
          break;
        }
        prevItem = separator[i];
      }
  
      if (validRange.start) {
        var query = doc.getRange(validRange.start, validRange.end, false);
  
        for (var i = 0; i < query.length; i++) {
          var lineText = query[i];
          eachWord(lineText, function(word) {
            var wordUpperCase = word.toUpperCase();
            if (wordUpperCase === aliasUpperCase && getTable(previousWord))
              table = previousWord;
            if (wordUpperCase !== sqlqueryOpts.CONS.ALIAS_KEYWORD)
              previousWord = word;
          });
          if (table) break;
        }
      }
      return table;
    }

    CodeMirror.registerHelper("hint", "sqlquery", function(editor, options) {
      var cur = editor.getCursor();
      var result = [];
      var token = editor.getTokenAt(cur), start, end, search;
      if (token.end > cur.ch) {
        token.end = cur.ch;
        token.string = token.string.slice(0, cur.ch - token.start);
      }
      if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) {
          search = token.string;
          start = token.start;
          end = token.end;
      } else {
          start = end = cur.ch;
          search = "";
      }
      var isInnerAttr = options && options.isInnerAttr;
      if(isInnerAttr && /['"]/.test(token.string.charAt(0))){
          start=token.start+1;
          search=token.string.substr(1,token.string.length-1);
          var m=search.match(new RegExp("[a-z0-9_]+$","i"));
          if(m){
            start=token.start+token.string.length-m[0].length;
            search=m[0];
          }
      }
      
      sqlqueryOpts.allTables = options.allTables?options.allTables:{};
      sqlqueryOpts.schemaTypes = options.schemaTypes;
      
      try{
        var tablesList = [];
        if(sqlqueryOpts.allTables){
          for(var key in sqlqueryOpts.allTables){
             if(tablesList.length<sqlqueryOpts.limitNum){
                tablesList.push(key);
             }
           }
        }
        options.tables = options.tables || {};
        sqlqueryOpts.tables = options.tables;
        if (tablesList) {
          for (var ki = 0; ki < tablesList.length; ki++) {
            var key = tablesList[ki];
            if (!sqlqueryUtils.objectContains(options.tables, key)) {
              options.tables[key] = [];
            }
          }
        }
      }catch(e){
        console.log(e);
      }
      
      sqlqueryOpts.keywords=getKeywords(editor);
      sqlqueryOpts.tables = parseTables(options && options.tables);
      var disableKeywords = options && options.disableKeywords;
      sqlqueryOpts.identifierQuote = getIdentifierQuote(editor);
      
      if(!sqlqueryOpts.defaultTable){
        var defaultTableName = options && options.defaultTable;
        if(defaultTableName){
          sqlqueryOpts.defaultTable = getTable(defaultTableName);
        }
        if (defaultTableName && !sqlqueryOpts.defaultTable)
          sqlqueryOpts.defaultTable = findTableByAlias(defaultTableName, editor);
  
        sqlqueryOpts.defaultTable = sqlqueryOpts.defaultTable || [];
        if (sqlqueryOpts.defaultTable.columns)
            sqlqueryOpts.defaultTable = sqlqueryOpts.defaultTable.columns;
      }
      
      var disableFlag=false;
      try{
          var prevChar = editor.getRange({ line:  cur.line, ch: token.start - 1}, { line:  cur.line, ch: token.start});
          if(prevChar==="."){
              disableFlag=true;
          }
      }catch(b){
      }
      //去除表前缀
      var schemaTypeLength=0;
      if(search.charAt(0) == "." && search.length>0){
          var tokenx = editor.getTokenAt({line: cur.line, ch: cur.ch - search.length});
          var prevchar = tokenx && tokenx.string;
          if(prevchar && prevchar===sqlqueryOpts.defaultMain){
            sqlqueryOpts.tmpSearch=search.slice(1);
          }
          else if(prevchar && sqlqueryUtils.objectContains(sqlqueryOpts.schemaTypes,prevchar)){
            sqlqueryOpts.tmpSearch=prevchar+search;
            schemaTypeLength=prevchar.length;
          }else{
            sqlqueryOpts.tmpSearch=search.slice(1);
          }
      }else if(search.charAt(0) !== sqlqueryOpts.identifierQuote && search === token.string || isInnerAttr){
         sqlqueryOpts.tmpSearch=search;
      }
      else{
        sqlqueryOpts.tmpSearch="";
      }
      if (sqlqueryOpts.tmpSearch.indexOf(".")===-1 && (search.charAt(0) == "." || search.charAt(0) == sqlqueryOpts.identifierQuote)) {
        start = nameCompletion(cur, token, result, editor);
      } else {
        var objectOrClass = function(w, className) {
          if (typeof w === "object") {
            w.className = className;
          } else {
            w = { text: w, className: className };
          }
          return w;
        };
        
        addMatches(result, search, sqlqueryOpts.defaultTable, function(w) {
            return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
        });
        
        isInnerAttr?null:addMatches(
            result,
            search,
            sqlqueryOpts.tables, function(w) {
              return objectOrClass(w, "CodeMirror-hint-table");
            }
        );
        
        if(!disableFlag){
         if (!disableKeywords)
           isInnerAttr?null:addMatches(result, search, sqlqueryOpts.keywords, function(w) {
              return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
          });
        }
        
        if(result && result.length>0 ){
          if(!sqlqueryOpts.schemaOpen){
            for(var i=0,ln=result.length;i<ln; i++){
               result[i].displayText = result[i].text;
               if(result[i].text===sqlqueryOpts.defaultMain){
                result[i].displayInfo=sqlqueryOpts.defaultMainTitle;
               }else{
                result[i].displayInfo = result[i].displayInfo || sqlqueryOpts.allTables[result[i].text];
               }
               result[i].render = sqlqueryUtils.hintRender;
               var txts=result[i].text.split(".");
               if(sqlqueryOpts.schemaType && txts.length>1 && txts[0].toLowerCase()!==sqlqueryOpts.schemaType.toLowerCase()){
               }else{
                   result[i].text = txts[txts.length-1];
               }
            }
          }else{
             for(var i=0,ln=result.length;i<ln; i++){
               result[i].displayText = result[i].text;
               if(result[i].text===sqlqueryOpts.defaultMain){
                 result[i].displayInfo=sqlqueryOpts.defaultMainTitle;
               }else{
                 result[i].displayInfo = result[i].displayInfo || sqlqueryOpts.allTables[result[i].text];
               }
               result[i].render = sqlqueryUtils.hintRender;
             }
          }
        }
      }
      return {list: result, from: CodeMirror.Pos(cur.line, start-schemaTypeLength), to: CodeMirror.Pos(cur.line, end)};
    });
}

//方便使用方法，也避免冲突
const sqlqueryUtils = {
  //获取有限制集合
  getLimitObject:function(source,filter,currSelectTables){
    var limit = sqlqueryOpts.limitTableNum;
    var idx=0;
    var data={};
    if(!source){
        return data;
    }
    filter = filter&&filter.length>0?filter.toLowerCase():null;
    var schemaType=sqlqueryOpts.schemaType?(sqlqueryOpts.schemaType.toLowerCase()+"."):null;
    for(var key in source){
      if(idx>=limit){
         break;
      }
      var key2=key.toLowerCase();
      var flag=schemaType?key2.startsWith(schemaType):true;
      if(!flag && filter && key.indexOf(".")>-1){
         flag=filter.indexOf(".")>-1;
      }
      if(!filter || this.arrayContains(currSelectTables,key2) || (!sqlqueryOpts.likeMatch && key2.indexOf(filter)>=0) || (!flag && key2.startsWith(filter))
              || (sqlqueryOpts.likeMatch && flag && this.checkLikeOrder(filter,key))){
          data[key] =source[key];
          idx++;
      }
    }
    return data;
  },
  /** 获取当前使用的表 */
  getSelectTables: function (editor, alltablecodes, schemaType) {
    var options=editor.getOption("hintOptions");
    sqlqueryOpts.tmpSearch = "";
    sqlqueryOpts.tempSqlTables = {};
    sqlqueryOpts.sqlTables = {};
    sqlqueryOpts.schemaType = schemaType || null;
    var currSelectTables = [];
    
    sqlqueryOpts.showPath = typeof options.showPath !== 'undefined' ? options.showPath : sqlqueryOpts.showPath;
    sqlqueryOpts.showType = typeof options.showType !== 'undefined' ? options.showType : sqlqueryOpts.showType;
    sqlqueryOpts.likeMatch = typeof options.likeMatch !== 'undefined' ? options.likeMatch : sqlqueryOpts.likeMatch;
    sqlqueryOpts.limitTableNum = typeof options.limitTableNum !== 'undefined' ? options.limitTableNum : sqlqueryOpts.limitTableNum;
    sqlqueryOpts.limitNum = typeof options.limitNum !== 'undefined' ? options.limitNum : sqlqueryOpts.limitNum;
    sqlqueryOpts.schemaOpen = typeof options.schemaOpen !== 'undefined' ? options.schemaOpen : sqlqueryOpts.schemaOpen;
    sqlqueryOpts.schemaQuery = typeof options.schemaQuery !== 'undefined' ? options.schemaQuery : sqlqueryOpts.schemaQuery;
    sqlqueryOpts.customCamelCases = typeof options.customCamelCases !== 'undefined' ? options.customCamelCases : sqlqueryOpts.customCamelCases;
    sqlqueryOpts.autoCamelCases = typeof options.autoCamelCases !== 'undefined' ? options.autoCamelCases : sqlqueryOpts.autoCamelCases;
    sqlqueryOpts.defaultMain = typeof options.defaultMain !== 'undefined' ? options.defaultMain : sqlqueryOpts.defaultMain;
    sqlqueryOpts.defaultMainTitle = typeof options.defaultMainTitle !== 'undefined' ? options.defaultMainTitle : sqlqueryOpts.defaultMainTitle;

    var cur = editor.getCursor();
    var token = editor.getTokenAt(cur);
    if (token.end > cur.ch) {
      token.end = cur.ch;
      token.string = token.string.slice(0, cur.ch - token.start);
    }
    try {
      var curSqlTag = sqlqueryOpts.CONS.QUERY_DIV;
      var openMybatis = editor.options.sqlMode;
      var allLineStr;
      if(openMybatis){
        var context = token.state.context;
        if(context){
          var f = context.isSqlTag;
          while(!f && context.prev){
             f = context.prev.isSqlTag;
             context = context.prev;
          }
          if(f){
            curSqlTag = context.tagName;
          }
        }
        allLineStr = editor.getValue(" ");
      }
      var inputAlias;
      var useIdentifierQuotes = false;
      var nameParts = [];
      var cont = true;
      var tokenx = token;
      while (cont) {
        cont = (tokenx.string.charAt(0) == ".");
        useIdentifierQuotes = useIdentifierQuotes || (tokenx.string.charAt(0) == sqlqueryOpts.identifierQuote);
        nameParts.unshift(this.cleanName(tokenx.string));

        tokenx = editor.getTokenAt(CodeMirror.Pos(cur.line, tokenx.start));
        if (tokenx.string == ".") {
          cont = true;
          tokenx = editor.getTokenAt(CodeMirror.Pos(cur.line, tokenx.start));
        }
      }
      inputAlias = nameParts[0];

      if (!inputAlias.match(new RegExp("^" + sqlqueryOpts.alisaRuleStr + "$", "i"))) {
        inputAlias = null;
      }

      var startLine = cur.line;
      var startLineStr = editor.getLine(startLine);
      if (inputAlias) {
        startLineStr = startLineStr.substr(0, cur.ch - 1 - (inputAlias.length)) + " " + sqlqueryOpts.alisaTempKey + " " + startLineStr.substr(cur.ch, startLineStr.length);
      }

      startLineStr = !openMybatis?this.clearContextComment(startLineStr):startLineStr;
      while (!openMybatis && !startLineStr.startsWith(curSqlTag) && startLine > 0
         || (openMybatis && editor.getLine(startLine).indexOf("<"+curSqlTag) ===-1 && startLine>0)) {
        startLine--;
        var startLineStr2 = !openMybatis?this.clearContextComment(editor.getLine(startLine)):editor.getLine(startLine);
        if (startLineStr2 && startLineStr2.length > 0) {
          if(openMybatis){
            startLineStr = editor.getLine(startLine) +" "+ startLineStr;
          }
          else if (startLineStr2.indexOf(curSqlTag) === -1) {
            startLineStr = startLineStr2.trim() + "\n" + startLineStr;
          } else {
            var tarrs = startLineStr2.split(curSqlTag);
            startLineStr = curSqlTag + tarrs[tarrs.length - 1] + "\n" + startLineStr;
            break;
          }
        }
      }
      var endLine = cur.line;
      while (endLine < editor.lineCount() - 1 && ( (!openMybatis && !startLineStr.endsWith(curSqlTag)) 
        || (openMybatis && editor.getLine(endLine).indexOf("</"+curSqlTag+">") === -1 ))) {
        endLine++;
        var endLineStr2 = !openMybatis?this.clearContextComment(editor.getLine(endLine)):editor.getLine(endLine);
        if (endLineStr2 && endLineStr2.length > 0) {
          if(openMybatis){
            startLineStr = startLineStr +" "+ editor.getLine(endLine);
          }
          else if (endLineStr2.indexOf(curSqlTag) === -1) {
            startLineStr = startLineStr + "\n" + endLineStr2.trim();
          } else {
            var tarrs = endLineStr2.split(curSqlTag);
            startLineStr = startLineStr + "\n" + tarrs[0] + curSqlTag;
            break;
          }
        }
      }
      
      startLineStr = !openMybatis?this.getRefTagContent(startLineStr):editor.getMode().getRefTagContext(
          sqlqueryUtils.clearContextValue(startLineStr), sqlqueryUtils.clearContextValue(allLineStr));
      if (startLineStr && startLineStr.length > 10) {
        for (var key in alltablecodes) {
          var tmp=key.split(".");
          
          var regxstr1="";
          if (tmp.length > 1) {
             regxstr1 = regxstr1 + sqlqueryOpts.schemaTableRuleStr.replace(/%s/g, tmp[1])
                 +"|"+sqlqueryOpts.tableRuleStr.replace(/%s/g, tmp[1])
          }else{
             regxstr1 = regxstr1 + sqlqueryOpts.tableRuleStr.replace(/%s/g, key);
          }
          regxstr1 = regxstr1.replace(/\./g, "\\.");
          var mresult = startLineStr.match(new RegExp(regxstr1, "ig"));
          if(!mresult){
             continue;  
          }
          var result=false;
          for (var i = 0, ln = mresult.length; i < ln; i++) {
              var newcode = mresult[i].replace(sqlqueryOpts.bracketRuleClear,"");
              startLineStr = startLineStr.replace(new RegExp(mresult[i],"g"), newcode);
              if(key.toLowerCase()===newcode.toLowerCase() || (tmp.length > 1 && newcode.indexOf(".")===-1
                   && tmp[1].toLowerCase()===newcode.toLowerCase())){
                result=true;
              }
          }
          if (result) {
            sqlqueryOpts.tempSqlTables[key] = [];
            currSelectTables.push(key);
          }
          if (currSelectTables.length > 50) {
            //正常不应该有这么多表。也不建议所有sql放一起
            break;
          }
        }
        sqlqueryOpts.currSelectQueryStr = startLineStr;
        sqlqueryOpts.currInputAlias = inputAlias;
      }
//      console.log("startLineStr",startLineStr,currSelectTables);
    } catch (e) {
      console.log(e);
    }
    return currSelectTables;
  },
  /** 设置当前使用的表字段 */
  setTableColumns: function (editor, currSelectTables, schemaType) {
    try {
      var options=editor.getOption("hintOptions");
      sqlqueryOpts.tmpSearch = "";
      sqlqueryOpts.schemaType = schemaType || null;
      sqlqueryOpts.tempSqlTables = {};
      sqlqueryOpts.sqlTables = options.sqlTables;
      if (!sqlqueryOpts.sqlTables || !currSelectTables || currSelectTables.length === 0) {
        return;
      }
      options.tables = {};
      sqlqueryOpts.tables = options.tables;
      
      var singleTableFlag = currSelectTables.length === 1;
      var subSelectFlag = new RegExp("FROM *\\(", "i").test(sqlqueryOpts.currSelectQueryStr);
      singleTableFlag = singleTableFlag && !subSelectFlag;
      
      var regxstr1;
      for (var i = 0, ln = currSelectTables.length; i < ln; i++) {
          regxstr1=(i==0?"":regxstr1+"|");
          if (currSelectTables[i].indexOf(".") > -1) {
             regxstr1 = regxstr1 + currSelectTables[i] + "|(?<!.)" + currSelectTables[i].split(".")[1];
          }else{
             regxstr1 = regxstr1 + "(?<!.)" + currSelectTables[i];
          }
      }
      
      regxstr1 = regxstr1.replace(/\./g, "\\.");
      var tableWithAliasRegs = new RegExp("\\b(" + regxstr1 + ")\\b +" + sqlqueryOpts.alisaRuleStr + "|\\b(" + regxstr1 + ")\\b", "ig");
      var tableWithAlias = sqlqueryOpts.currSelectQueryStr.match(tableWithAliasRegs);
      
      var handlerTableAlias = function (tableAliasMap, str) {
        var t = str.split(" ");//table t1
        var factkey = sqlqueryUtils.getArrayValue(currSelectTables, t[0]);
        if (factkey) {
          if (!tableAliasMap.hasOwnProperty(factkey)) {
            tableAliasMap[factkey] = [""];
          }
          var code;
          if(t.length===1){
             code = sqlqueryOpts.defaultMain;
          }else{
             code = !sqlqueryUtils.objectContains(sqlqueryOpts.keywords, t[1]) ? t[1] : "";
          }
          if (!sqlqueryUtils.arrayContains(tableAliasMap[factkey], code)) {
            tableAliasMap[factkey].push(code);
          }
        }
      };
      var tableAliasMap = {};
      var hasAlisaListMap = {};
      var alisaTables = [];
      if (tableWithAlias && tableWithAlias.length > 0) {
        if (singleTableFlag) {
          handlerTableAlias(tableAliasMap, tableWithAlias[0]);

          this.dynamicAlisaColumns(tableAliasMap, sqlqueryOpts.tables, sqlqueryOpts.sqlTables, hasAlisaListMap, alisaTables);
        } else {
          for (var ki = 0; ki < tableWithAlias.length; ki++) {
            handlerTableAlias(tableAliasMap , tableWithAlias[ki]);
          }
          this.dynamicAlisaColumns(tableAliasMap, sqlqueryOpts.tables, sqlqueryOpts.sqlTables, hasAlisaListMap, alisaTables);
        }
      }
      
      for (var key in sqlqueryOpts.sqlTables) {
         sqlqueryOpts.tempSqlTables[key] = sqlqueryOpts.sqlTables[key];
      }
      if (!singleTableFlag && sqlqueryOpts.currInputAlias) {
        try {
          this.analysisAlisaCompletion(sqlqueryOpts.tempSqlTables, sqlqueryOpts.tables, currSelectTables, sqlqueryOpts.currInputAlias, sqlqueryOpts.currSelectQueryStr, sqlqueryOpts.alisaTempKey);
        } catch (b1) {
          console.log(b1);
        }
      }
      if (alisaTables.length > 0) {
        var hintx = function (CodeMirror, self, data) {
          sqlqueryOpts.tmpSearch = "";
          CodeMirror.replaceRange(data.displayText, { line: self.from.line, ch: self.from.ch }, { line: self.to.line, ch: self.to.ch });
        }
        var dm=[],dm2=[];
        for (var i = 0, ln = alisaTables.length; i < ln; i++) {
          var t={},t2={};
          const alisaTablex=alisaTables[i];
          for(key in alisaTablex){
              t[key]=alisaTablex[key];
              t2[key]=alisaTablex[key];
          }
          t["hint"] = hintx;
          dm.push(t);
          dm2.push(t2);
        }
        sqlqueryOpts.tables[sqlqueryOpts.defaultMain] = dm;
        sqlqueryOpts.defaultTable = dm2;
      }
//      console.log("currInputAlias>>",sqlqueryOpts.currInputAlias,tableWithAlias,singleTableFlag,tableAliasMap);
    } catch (b) {
      console.log(b);
    }
  },

  cleanName: function (name) {
    // Get rid name from identifierQuote and preceding dot(.)
    if (name.charAt(0) == ".") {
      name = name.substr(1);
    }
    // replace duplicated identifierQuotes with single identifierQuotes
    // and remove single identifierQuotes
    var nameParts = name.split(sqlqueryOpts.identifierQuote + sqlqueryOpts.identifierQuote);
    for (var i = 0; i < nameParts.length; i++)
      nameParts[i] = nameParts[i].replace(new RegExp(sqlqueryOpts.identifierQuote, "g"), "");
    return nameParts.join(sqlqueryOpts.identifierQuote);
  },

  checkLikeOrder: function (search, word) {
    search=search.toUpperCase();
    word=word.toUpperCase();
    var index1 = 0;
    var index2 = 0;
    while (index1 < search.length && index2 < word.length) {
      if (search[index1] === word[index2]) {
        index1++;
      }
      index2++;
    }
    return index1 === search.length;
  },

  arrayContains: function (arr, val) {
    if (!arr) {
      return false;
    }
    for (var i = 0; i < arr.length; i++) {
      if (arr[i].toLowerCase() === val.toLowerCase()) {
        return true;
      }
    }
    return false;
  },
  getArrayValue: function (arr, val) {
    if (!arr) {
      return null;
    }
    for (var i = 0; i < arr.length; i++) {
      if (arr[i].toLowerCase() === val.toLowerCase()) {
        return arr[i];
      } else if (sqlqueryOpts.schemaType && arr[i].toLowerCase() === (sqlqueryOpts.schemaType + "." + val).toLowerCase()) {
        return arr[i];
      }
    }
    return null;
  },
  objectContains: function (obj, key) {
    if (!obj) {
      return false;
    }
    for (var k in obj) {
      if (k.toLowerCase() === key.toLowerCase()) {
        return true;
      }
    }
    return false;
  },
  hintRender: function (element, self, data) {
    let div = document.createElement("div");
    div.setAttribute("class", "autocomplete-div");
    let divText = document.createElement("div");
    divText.setAttribute("class", "autocomplete-name");
    var type = sqlqueryOpts.showType && data.ctype?'<span class="hint-type"> '+data.ctype+'</span>':"";
    divText.innerHTML = sqlqueryUtils.markCharOrder(sqlqueryOpts.tmpSearch, data.displayText) + type;
    div.appendChild(divText);

    var showtitle;
    if (data.displayInfo && data.displayInfo !== data.displayText) {
      showtitle = data.displayInfo;
      if (showtitle.length > sqlqueryOpts.titleMaxLength) {
          showtitle=showtitle.substr(0, sqlqueryOpts.titleMaxLength);
      }
      if (sqlqueryOpts.showPath && data.displayTitle) {
        showtitle = showtitle + "（" + data.displayTitle + "）";
      }
    } else if (sqlqueryOpts.showPath && data.displayTitle) {
      showtitle = data.displayTitle;
    }
    if (showtitle) {
      let divInfo = document.createElement("div");
      divInfo.setAttribute("class", "autocomplete-hint");
      divInfo.innerText = showtitle;

      let divInfoHidden = document.createElement("div");
      divInfoHidden.setAttribute("class", "autocomplete-hidden");
      divInfoHidden.innerText = showtitle + " ";
      divText.appendChild(divInfoHidden);
      div.appendChild(divInfo);
      
      if (data.displayTitle) {
        var titledtl = sqlqueryOpts.allTables[data.displayTitle];
        if (titledtl && titledtl.length > 0 && titledtl !== data.displayTitle) {
          divInfo.setAttribute("title", data.displayTitle+ "  " + titledtl);
        }
      }
    }
    
    var moreDivText = document.getElementById("CodeMirror-hints-more-info");
    if(!moreDivText){
        moreDivText = document.createElement("div");
        moreDivText.setAttribute("class", "more-info");
        moreDivText.setAttribute("id", "CodeMirror-hints-more-info");
    }
    const cdetail=data.cdetail;
    const displayText=data.displayText;
    div.addEventListener("mouseenter", function() {
       var divhints = document.getElementsByClassName("CodeMirror-hints")[0];
       moreDivText.innerHTML="";
       if(!document.getElementById("CodeMirror-hints-more-info")){
           divhints.appendChild(moreDivText);
       }
       if(cdetail){
         var moreTitleDiv = document.createElement("div");
         moreTitleDiv.setAttribute("class", "more-info-title");
         moreTitleDiv.innerText = displayText;
         moreDivText.appendChild(moreTitleDiv);
         moreDivText.style.top = parseInt(divhints.offsetTop)+"px";
         moreDivText.style.left = parseInt(divhints.offsetLeft+divhints.offsetWidth)+"px";
         var cdetailDiv = document.createElement("div");
         cdetailDiv.setAttribute("class", "more-info-body");
         cdetailDiv.innerHTML = cdetail;
         moreDivText.appendChild(cdetailDiv);
         moreDivText.style.display="block";
       }else{
         moreDivText.style.display="none";
       }
    });
    div.addEventListener("mouseleave", function() {
       if(moreDivText){
          moreDivText.remove();
       }
    });
    element.appendChild(div);
  },

  displayHandler: function (text, displayInfo, displayTitle, ctype, cdetail) {
    return {
      text: text,
      displayText: text,
      displayInfo: displayInfo,
      displayTitle: displayTitle,
      ctype: ctype,
      cdetail: cdetail,
      render: this.hintRender
    };
  },

  markCharOrder: function (search, word) {
    if (!search) {
      return word;
    }
    var markHandler=function(word){
        var index1 = 0;
        var index2 = 0;
        var ids = [];
        while (index1 < search.length && index2 < word.length) {
            if (search[index1].toUpperCase() === word[index2].toUpperCase()) {
              ids.push(index2);
              index1++;
            }
            index2++;
        }
        if(index1!==search.length){
          return null;
        }
        for (var i = ids.length - 1; i >= 0; i--) {
            var char = '<span class="match-char">' + word.slice(ids[i], ids[i] + 1) + '</span>';
            word = word.slice(0, ids[i]) + char + word.slice(ids[i] + 1);
        }
        return word;
    };
  
    if(sqlqueryOpts.likeMatch && search.indexOf(".")==-1 && word.indexOf(".")>-1){
      var arrs=word.split(".");
      if(search.length<=arrs[1].length){
        var word2=markHandler(arrs[1]);
        if(word2){
             word= arrs[0] + "." + word2;
             return word;
        }
      }
    }
    word = markHandler(word);
    return word;
  },

  dynamicAlisaColumns: function (tableAliasMap, tables, sqlTables, hasAlisaListMap, alisaTables) {

    var columnsHandler = function (a1, a2, tables, alisa, key, ctype, cdetail) {
      if(!a1){
        return;
      }
      if (!tables[alisa]) {
          tables[alisa] = [];
      }
      for (var i = 0, ln = a1.length; i < ln; i++) {
        if (a2) {
           tables[alisa].push(sqlqueryUtils.displayHandler(a1[i], a2[i], key, ctype && ctype[i], cdetail && cdetail[i]));
        } else {
           tables[alisa].push(a1[i]);
        }
      }
    };
    var columnsHandler2 = function (a1, a2, alisaTables, key, ctype, cdetail,autoCamelCases) {
      if(!a1){
        return;
      }
      for (var i = 0, ln = a1.length; i < ln; i++) {
        if (a2) {
          alisaTables.push(sqlqueryUtils.displayHandler(autoCamelCases?sqlqueryUtils.convertToCamelCase(a1[i]):a1[i], a2[i], key, ctype && ctype[i], cdetail && cdetail[i]));
        } else {
          alisaTables.push(autoCamelCases?sqlqueryUtils.convertToCamelCase(a1[i]):a1[i]);
        }
      }
    };
    for (var key in tableAliasMap) {
      var t = sqlTables[key];
      if (t && t.length > 0) {
        var arrs = tableAliasMap[key];
        if (!arrs && arrs.length > 0) {
          continue;
        }
        var t0 = t[0];
        var t1 = t.length > 1 && t[1];
        var t2 = t.length > 2 && t[2];
        var t3 = t.length > 3 && t[3];
        var t4 = t.length > 4 && t[4];
        for (var i = 0, ln = arrs.length; i < ln; i++) {
          var alisa = arrs[i];
          if (i === 0) {
            if(alisa!=sqlqueryOpts.defaultMain){
                columnsHandler(t0, t1, tables, key, key, t2, t3);
            }
            columnsHandler2(t0, t1, alisaTables, key, t2, t3);
            if(sqlqueryOpts.customCamelCases){
                columnsHandler2(t4, t1, alisaTables, key, t2, t3);
            }else if(sqlqueryOpts.autoCamelCases){
                columnsHandler2(t0, t1, alisaTables, key, t2, t3,true);
            }
          }
          if (alisa !== "" && alisa!=sqlqueryOpts.defaultMain) {
            tables[alisa] = [];
            columnsHandler(t0, t1, tables, alisa, key, t2, t3);
          }
        }
      }
    }
  },

  analysisAlisaCompletion: function (tempSqlTables, tables, currTableCodes, inputAlias, text, currKey) {
    if (!text) {
      return;
    }
    text=text.replace(new RegExp("\\b(INSERT INTO|CREATE TABLE)\\b.*?\\)( |AS)*(?=\\bSELECT\\b)", "i")," ");
    inputAlias = inputAlias.toLowerCase();
    var groupdata = this.getNestingDepth(currTableCodes, text, currKey);
    if (!groupdata) {
      return;
    }
    var alisalist = [];
    var currdata = groupdata;

    var currAlisaHandler = function (data, alisalist) {
      for (var i = 0; i < data.length; i++) {
        var t = data[i];
        var alisa = t.alisa;
        if (alisa) {
          if (!sqlqueryUtils.arrayContains(alisalist, alisa)) {
            alisalist.push(alisa.toLowerCase());
          }
        }
        if (t.hasOwnProperty("currlevel") && t.currlevel === 0) {
          currlevel = t.currlevel;
        }
        if (t.children) {
          if (t.hasOwnProperty("currlevel") && t.currlevel === 1) {
            currdata = t.children;
          }
          currAlisaHandler(t.children, alisalist);
        }
      }
    };
    currAlisaHandler(groupdata, alisalist);

    if (!currdata) {
      return;
    }

    var defaultFieldsHandler = function (t, inputAlias) {
      var factcode = sqlqueryUtils.getArrayValue(currTableCodes, t.code);
      if (factcode) {
        tables[inputAlias] = [];
        var t = tempSqlTables[factcode];
        if (t && t.length > 0) {
          for (var i = 0, ln = t[0].length; i < ln; i++) {
            tables[inputAlias].push(sqlqueryUtils.displayHandler(t[0][i], t[1] ? t[1][i] : "", factcode, t[2] ? t[2][i] : "", t[3] ? t[3][i] : ""));
          }
        }
      }
    };

    var getFieldsHandler = function (t, inputAlias, firstFlag, ckey) {
      if (t.children && t.children.length > 0) {
        if (firstFlag) {
          tables[inputAlias] = [];
        }
        var children = t.children;
        var cmap = {};
        for (var ci = 0; ci < children.length; ci++) {
          var c = children[ci];
          if (firstFlag && c.code) {
            defaultFieldsHandler(c, c.alisa);
          }
          if (c.alisa) {
            cmap[c.alisa.toLowerCase()] = c;
          }
        }
        for (var ci = 0; ci < children.length; ci++) {
          var c = children[ci];
          if (!(alisalist.length > 0 && c.fields)) {
            continue;
          }
          var fields = c.fields.match(new RegExp("((" + alisalist.join("|") + ")\\." + sqlqueryOpts.codeRuleStr + " (AS| +) " + sqlqueryOpts.codeAlisaRuleStr + " *(,|$))" +
            "|((" + alisalist.join("|") + ")\\." + sqlqueryOpts.codeRuleStr + "[^,]+ (AS| ) " + sqlqueryOpts.codeAlisaRuleStr + " *(,|$))" +
            "|([^,]*\\(.*?\\).*?(AS| +) " + sqlqueryOpts.codeAlisaRuleStr + " *(,|$))" +
            "|((" + alisalist.join("|") + ")\\.\\* *(,|$))|((" + alisalist.join("|") + ")\\." + sqlqueryOpts.codeRuleStr + " *(,|$))" +
            "|((?<!\\.)('\\d+'|\\d+) (AS| +) " + sqlqueryOpts.codeAlisaRuleStr + " *(,|$))", "ig"));
          if (fields && fields.length > 0) {
            for (var j = 0; j < fields.length; j++) {
              var fstr = fields[j];
              if (fstr) {
                fstr = fstr.trim().replace(/ *,$/g, "");
                var fm = fstr.match(new RegExp("^(" + alisalist.join("|") + ")\\.\\*$", "i"));
                var endFlag = false;
                if (fm) {
                  //fm[1]
                  endFlag = true;
                  //console.log("fm1---"+JSON.stringify(fm));
                  var cdata = cmap[fm[1].toLowerCase()];
                  if (cdata && cdata.code) {
                    var factcode = sqlqueryUtils.getArrayValue(currTableCodes, cdata.code);
                    if (factcode) {
                      var t = tempSqlTables[factcode];
                      if (t && t.length > 0) {
                        for (var i = 0, ln = t[0].length; i < ln; i++) {
                          if (!firstFlag && ckey !== "*") {
                            continue;
                          }
                          tables[inputAlias].push(sqlqueryUtils.displayHandler(t[0][i], t[1] ? t[1][i] : "", factcode, t[2] ? t[2][i] : "", t[3] ? t[3][i] : ""));
                        }
                      }
                    }
                  } else {
                    getFieldsHandler(cdata, inputAlias, false, "*");
                  }
                }
                if (!endFlag) {
                  fm = fstr.match(new RegExp("^(" + alisalist.join("|") + ")\\.(" + sqlqueryOpts.codeRuleStr + ")$", "i"));
                }
                if (!endFlag && fm) {
                  //fm[1] fm[2]
                  endFlag = true;
                  var cdata = cmap[fm[1].toLowerCase()];
                  if (cdata && cdata.code) {
                    var factcode = sqlqueryUtils.getArrayValue(currTableCodes, cdata.code);
                    if (factcode) {
                      var t = tempSqlTables[factcode];
                      if (t && t.length > 0) {
                        for (var i = 0, ln = t[0].length; i < ln; i++) {
                          if (!firstFlag && ckey.toLowerCase() !== fm[2].toLowerCase()) {
                            continue;
                          }
                          if (fm[2].toLowerCase() === t[0][i].toLowerCase()) {
                            tables[inputAlias].push(sqlqueryUtils.displayHandler(t[0][i], t[1] ? t[1][i] : "", factcode, t[2] ? t[2][i] : "", t[3] ? t[3][i] : ""));
                          }
                        }
                      }
                    }
                  } else {
                    getFieldsHandler(cdata, inputAlias, false, fm[2]);
                  }
                }
                if (!endFlag) {
                  fm = fstr.match(new RegExp("^(" + alisalist.join("|") + ")\\.(" + sqlqueryOpts.codeRuleStr + ") AS (" + sqlqueryOpts.codeAlisaRuleStr + ")$", "i"));
                  if (!fm) {
                    fm = fstr.match(new RegExp("^(" + alisalist.join("|") + ")\\.(" + sqlqueryOpts.codeRuleStr + ") AS (" + sqlqueryOpts.codeRuleStr + ")$", "i"));
                  }
                }
                if (!endFlag && fm) {
                  // fm[1] fm[2] fm[4]/fm[9]
                  endFlag = true;
                  var cdata = cmap[fm[1].toLowerCase()];
                  if (cdata && cdata.code) {
                    var factcode = sqlqueryUtils.getArrayValue(currTableCodes, cdata.code);
                    if (factcode) {
                      var t = tempSqlTables[factcode];
                      if (t && t.length > 0) {
                        for (var i = 0, ln = t[0].length; i < ln; i++) {
                          if (!firstFlag && ckey.toLowerCase() !== fm[9].toLowerCase()) {
                            continue;
                          }
                          if (fm[2].match(new RegExp(sqlqueryOpts.bracketRuleStr))) {
                            fm[2] = fm[2].substring(1, fm[2].length - 1);
                          }
                          if (fm[2].toLowerCase() === t[0][i].toLowerCase()) {
                            tables[inputAlias].push(sqlqueryUtils.displayHandler(fm[9], t[1] ? t[1][i] : "", factcode, t[2] ? t[2][i] : "", t[3] ? t[3][i] : ""));
                          }
                        }
                      }
                    }
                  } else {
                    getFieldsHandler(cdata, inputAlias, false, fm[9]);
                  }
                }
                if (!endFlag) {
                  fm = fstr.match(new RegExp("\\b(" + alisalist.join("|") + ")\\.(" + sqlqueryOpts.codeRuleStr + ")\\b|(?<= AS *)" + sqlqueryOpts.codeRuleStr + "$", "ig"));
                }
                if (!endFlag && fm) {
                  endFlag = true;
                  var keys = [];
                  var descs = [];
                  for (var mj = 0, fln = fm.length - 1; mj < fln; mj++) {
                    var tarr = fm[mj].split(".");
                    var cdata = cmap[tarr[0].toLowerCase()];
                    if (cdata && cdata.code) {
                      var factcode = sqlqueryUtils.getArrayValue(currTableCodes, cdata.code);
                      if (factcode) {
                        var t = tempSqlTables[factcode];
                        if (t && t.length > 0) {
                          for (var i = 0, ln = t[0].length; i < ln; i++) {
                            if (tarr[1].toLowerCase() === t[0][i].toLowerCase()) {
                              if (!sqlqueryUtils.arrayContains(keys, factcode)) {
                                keys.push(factcode);
                              }
                              if (t[1] && !sqlqueryUtils.arrayContains(descs, t[1][i]) && descs.length < 5) {
                                descs.push(t[1][i]);
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                  if (keys.length > 0) {
                    tables[inputAlias].push(sqlqueryUtils.displayHandler(fm[fm.length - 1], descs.join("+"), keys.join("+")));
                  } else {
                    tables[inputAlias].push(sqlqueryUtils.displayHandler(fm[fm.length - 1], null, null));
                  }
                }
              }
            }
          }
        }

      }
    };

    var curralisamap = {};
    for (var i = 0; i < currdata.length; i++) {
      var t = currdata[i];
      var alisa = t.alisa;
      if (alisa) {
        curralisamap[alisa.toLowerCase()] = t;
      }
    }
    var t = curralisamap[inputAlias];
    if (t) {
      if (t.code) {
        defaultFieldsHandler(t, inputAlias);
      }
      getFieldsHandler(t, inputAlias, true, "");
    }
  },

  getNestingDepth: function (currTableCodes, sql, currKey) {
    var maxlevel = 0;
    var level = 0;
    var currlevel = null;
    var groups = [];
    var str = "";
    var lastend = "";
    var num = 0;
    for (var i = 0; i < sql.length; i++) {
      var c = sql[i];
      str = str + c;
      switch (c) {
        case '(':

          level++;
          break;
        case ')':
          if (maxlevel < level) {
            maxlevel = level;
          }

          level--;
          break;
        default:
          if (i === sql.indexOf(currKey)) {
            currlevel = level;
          }
      }
      var keym = str && str.match(new RegExp("( (JOIN|WHERE) )$", "i"));
      if ((level === 0 && keym) || i === sql.length - 1) {
        str = str.trim();
        if (lastend && lastend.toLowerCase() === "where") {
          str = "where " + str;
        }
        lastend = keym && keym[0].trim();
        var t = {
          sqlstr: str,
          idx: num,
          maxlevel: maxlevel,
        };
        groups.push(t);
        num++;
        if (currlevel != null) {
          t.currlevel = currlevel;
        }
        var subm2 = str.match(new RegExp("\\bFROM *\\( *SELECT\\b.*\\bFROM\\b.*\\) *(" + sqlqueryOpts.alisaRuleStr + ")", "i"));
        if (str.length > 1 && str.substr(0, 1) === "(" || subm2) {
          if (currlevel != null) {
            var idx = str.indexOf(currKey);
            var idx2 = idx + currKey.length;
            t.currlevel = t.currlevel - this.getNestingDepth2(currTableCodes, str, currKey, idx, idx2);
          }
          var m1 = str.match(new RegExp("\\((.*\\b(" + currTableCodes.join("|") + ")\\b.*)\\) *(" + sqlqueryOpts.alisaRuleStr + ")", "i"));
          if (m1) {
            t.alisa = m1[3];
            var cstr = m1[1];
            var children = this.getNestingDepth(currTableCodes, cstr, currKey);
            t.children = children;
          } else {
            t.iserror = true;
          }
        } else if (str.length > 5 && str.substr(0, 5).toLowerCase() === "where") {
          t.iswhere = true;
        } else {
          var m2 = str.match(new RegExp("\\b(" + currTableCodes.join("|") + ") (" + sqlqueryOpts.alisaRuleStr + ")\\b", "i"));
          if (m2) {
            t.alisa = m2[2];
            t.code = m2[1];

            m2 = str.match(new RegExp("(?<=^SELECT\\b +).*?(?= +\\bFROM\\b)", "i"));
            if (m2) {
              t.fields = m2[0];
            }
          } else {
            t.iserror = true;
          }
        }
        maxlevel = 0;
        currlevel = null;
        str = "";

      }

    }

    return groups;
  },

  getNestingDepth2: function (currTableCodes, sql, currKey, idx, idx2) {
    var num = 0;
    var str = "";
    var i = 0;
    var func = function (str, idx) {
      if (str.substr(0, 1) != "(" && idx - i >= 0) {
        i++;
        str = sql.substr(idx - i, i);
        return func(str, idx);
      } else {
        return str;
      }
    };
    str = func(str, idx);
    var j = 0;
    var str2 = "";
    var func2 = function (str2, idx2) {
      if (str2.substr(str2.length - 1, 1) != ")" && idx2 + j < sql.length) {
        j++;
        str2 = sql.substr(idx2, j);
        return func2(str2, idx2);
      } else {
        return str2;
      }
    };
    str = str + func2(str2, idx2);

    if (!str.match(new RegExp("\\b(" + currTableCodes.join("|") + ")\\b", "i"))) {
      num++;
      num = num + this.getNestingDepth2(currTableCodes, sql, currKey, idx - i, idx2 + j);
    } else {
    }
    return num;
  },

  getRefTagContent: function (text1) {
    if (!text1) {
      return "";
    }
    text1 = this.clearContextValue(text1).replace(/(\\n|\n|\\t|\t)/g, " ")
      .replace(/ +/g, " ").replace(sqlqueryOpts.regxSemicolon, "");
    text1 = this.clearContextComment(text1, true);
    return text1;
  },

  clearContextComment: function (text, clearFlag) {
    if (!text) {
      return "";
    }
    text = this.clearContextValue(text).replace(/''/ig, " ");
    var lines = text.split('\n');
    var text2 = "";
    for (var i = 0; i < lines.length; i++) {
      var linex = lines[i];
      var f = linex.match(sqlqueryOpts.regxComment);
      if (f && f[0]) {
        continue;
      }
      linex = linex.replace(sqlqueryOpts.regxComment2, " ");
      if (clearFlag) {
        var arr = linex.split(sqlqueryOpts.CONS.QUERY_DIV);
        linex = "";
        for (var j = 0; j < arr.length; j++) {
          if (arr[j] && arr[j].indexOf(sqlqueryOpts.alisaTempKey) !== -1) {
            linex = arr[j];
            break;
          }
        }
        if (linex === "") {
          linex = lines[i];
        }
      }
      if (!linex || linex.length === 0) {
        continue;
      }
      if (text2 === "") {
        text2 = linex;
      } else {
        text2 = text2 + "\n" + linex;
      }
    }
    text2 = text2.replace(/(\\n|\n|\\t|\t)/g, " ").replace(/ +/g, " ");
    return text2;
  },
  clearContextComment2: function (text) {
    if (!text) {
      return "";
    }
    text = text.replace(/(\\n|\n|\\t|\t)/g, "\n");
    var lines = text.split('\n');
    var text2 = "";
    for (var i = 0; i < lines.length; i++) {
      var linex = lines[i];
      var f = linex.match(sqlqueryOpts.regxComment);
      if (f && f[0]) {
        continue;
      }
      linex = linex.replace(sqlqueryOpts.regxComment2, " ");
      if (!linex || linex.length === 0) {
        continue;
      }
      if (text2 === "") {
        text2 = linex;
      } else {
        text2 = text2 + "\n" + linex;
      }
    }
    return text2;
  },
  clearContextValue: function (text1) {
    if (!text1) {
      return "";
    }
    text1 = text1.replace(/ +/g, " ").replace(new RegExp("'.*?'( AS\\b)*","ig"), (match) => {
      if (/\bAS\b/i.test(match)) {
         return match;
      } else {
         return "' '";
      }
    });
    return text1;
  },
  convertToCamelCase: function(str) {
    if (!str.includes('_')) {
      return str.toLowerCase();
    }
    const words = str.split('_');
    const camelCase = words.map((word, index) => {
      if (index === 0) {
        return word.toLowerCase();
      } else {
        return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
      }
    });
    return camelCase.join('');
  }
}
